有了前面簡單ExpandoObject Dynamic Query例子的概念後,接著進到底層來了解Dapper如何細節處理,為何要自訂義DynamicMetaObjectProvider。
假設使用下面代碼
using (var cn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDB;Integrated Security=SSPI;Initial Catalog=master;"))
{
var result = cn.Query("select N'暐翰' Name,26 Age").First();
Console.WriteLine(result.Name);
}
取值的過程會是 : 建立動態Func > 保存在緩存 > 使用result.Name
> 轉成呼叫 ((DapperRow)result)["Name"]
> 從DapperTable.Values陣列
中以"Name"欄位對應的Index
取值
接著查看源碼GetDapperRowDeserializer方法,它掌管dynamic如何運行的邏輯,並動態建立成Func給上層API呼叫、緩存重複利用。
此段Func邏輯 :
DapperTable雖然是方法內的局部變數,但是被生成的Func引用,所以不會被GC
一直保存在記憶體內重複利用。
因為是dynamic不需要考慮類別Mapping,這邊直接使用GetValue(index)
向資料庫取值
var values = new object[select欄位數量];
for (int i = 0; i < values.Length; i++)
{
object val = r.GetValue(i);
values[i] = val is DBNull ? null : val;
}
public DapperRow(DapperTable table, object[] values)
{
this.table = table ?? throw new ArgumentNullException(nameof(table));
this.values = values ?? throw new ArgumentNullException(nameof(values));
}
private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider
{
DynamicMetaObject GetMetaObject(Expression parameter)
{
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
}
}
DapperRowMetaObject主要功能是定義行為,藉由override BindSetMember、BindGetMember
方法,Dapper定義了Get、Set的行為分別使用IDictionary<string, object> - GetItem方法
跟DapperRow - SetValue方法
最後Dapper利用DataReader的欄位順序性
,先利用欄位名稱取得Index,再利用Index跟Values取得值
可以思考一個問題 : 在DapperRowMetaObject可以自行定義Get跟Set行為,那麼不使用Dictionary - GetItem方法,改用其他方式,是否代表不需要繼承IDictionary<string,object>
?
Dapper這樣做的原因之一跟開放原則有關,DapperTable、DapperRow都是底層實作類別,基於開放封閉原則不應該開放給使用者
,所以設為private
權限。
private class DapperTable{/*略*/}
private class DapperRow :IDictionary<string, object>, IReadOnlyDictionary<string, object>,System.Dynamic.IDynamicMetaObjectProvider{/*略*/}
那麼使用者想要知道欄位名稱
怎麼辦?
因為DapperRow實作IDictionary所以可以向上轉型為IDictionary<string, object>
,利用它為公開介面
特性取得欄位資料。
public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable{/*略*/}
舉個例子,筆者有做一個小工具HtmlTableHelper就是利用這特性,自動將Dapper Dynamic Query轉成Table Html,如以下代碼跟圖片
using (var cn = "Your Connection")
{
var sourceData = cn.Query(@"select 'ITWeiHan' Name,25 Age,'M' Gender");
var tablehtml = sourceData.ToHtmlTable(); //Result : <table><thead><tr><th>Name</th><th>Age</th><th>Gender</th></tr></thead><tbody><tr><td>ITWeiHan</td><td>25</td><td>M</td></tr></tbody></table>
}